package util

import (
	"encoding/binary"
	"errors"
	"hash/crc32"
	"os"
	"sync"
)

const (
	RestartGap uint64 = 10000
)

type Sequencer struct {
	snapshotPath string
	maxId        uint64
	currentId    uint64
	diskError    int
	sequenceLock sync.Mutex
	snapshotLock sync.Mutex
}

func NewSequencer(path string, maxId uint64) (s *Sequencer, err error) {
	s = new(Sequencer)
	s.snapshotPath = path
	s.diskError = 0

	err = s.fetchSnapshot()
	if err != nil {
		s = nil
	}

	return s, err
}

func (s *Sequencer) NextId() uint64 {
	if s.diskError > 0 || s.currentId == s.maxId {
		return 0
	}

	s.sequenceLock.Lock()
	defer s.sequenceLock.Unlock()

	s.currentId++
	if s.currentId%RestartGap == 0 {
		s.snapshotLock.Lock()
		go s.snapshot(s.currentId)
	}

	return s.currentId
}

func (s *Sequencer) snapshot(snapshotId uint64) {
	defer s.snapshotLock.Unlock()

	snapshotFile, err := os.OpenFile(s.snapshotPath, os.O_RDWR|os.O_SYNC, 0666)
	if err != nil {
		s.diskError = 1
		return
	}

	buf := make([]byte, 12)
	binary.BigEndian.PutUint64(buf[:8], snapshotId)
	binary.BigEndian.PutUint32(buf[8:], crc32.ChecksumIEEE(buf[:8]))

	_, err = snapshotFile.Write(buf)
	snapshotFile.Close()

	if err != nil {
		s.diskError = 1
	}
}

func (s *Sequencer) fetchSnapshot() error {
	snapshotFile, err := os.OpenFile(s.snapshotPath, os.O_RDWR, 0666)
	if err != nil {
		return err
	}

	defer snapshotFile.Close()
	buf := make([]byte, 12)
	_, err = snapshotFile.Read(buf)
	if err != nil {
		return err
	}

	s.currentId = binary.BigEndian.Uint64(buf[:8])
	if s.currentId == 0 {
		return nil
	}

	s.currentId += RestartGap

	crc := crc32.ChecksumIEEE(buf[:8])
	storedCrc := binary.BigEndian.Uint32(buf[8:])
	if crc != storedCrc {
		err = errors.New("data corruption")
	}
	return err
}
